#include"threadpool.h"

const size_t MAX_TASK_THRESHHOLD = 4;

/*
example:

ThreadPool threadpool;
threadpool.startThreadPool(4);

class MyTask:public Task
{
public:
	void run(){...任务代码...}
}；

threadpool.sumMit(std::make_shared<MyTask>());
*/

			//////////////////////////////线程池方法实现BEGIN//////////////////////////////////

// 线程池的构造
ThreadPool::ThreadPool() :
	initThreadSize_(0), // 初始线程数量 0
	taskNum_(0), // 初始任务数量 0
	taskQueMaxThreshHold_(MAX_TASK_THRESHHOLD), // 初始任务队列的最大阈值
	poolmode_(PoolMode::MODE_FIXED)// 默认线程池模式为固定数量的线程池
{}

// 线程池的析构
ThreadPool::~ThreadPool() 
{}

// 设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode) 
{
	poolmode_ = mode;
}

// 设置task任务队列的上限阈值
void ThreadPool::setTaskQueMaxThreadHold(size_t threshHold) 
{
	taskQueMaxThreshHold_ = threshHold;
}

// 给线程池提交任务  用户调用该接口，传入任务对象，生产任务
Result ThreadPool::subMitTask(std::shared_ptr<Task> sp) // std::shared_ptr<Task> sp = std::make_shared<MyTask>()
{
	// 先获取锁        
	std::unique_lock<std::mutex> lock(taskQueMtx_); // 上锁

	// 线程的通信 等待任务队列有空间    
	// 等价于while(taskQue_.size() == taskQueMaxThreshHold_{notFull.wait(lock);})
	// 需求：用户提交任务最长不可以阻塞超过1s否则判断超时 任务提交失败 返回
	if (!notFull_.wait_for(lock, std::chrono::seconds(1),[&]()->bool
		{
			return taskQue_.size() < taskQueMaxThreshHold_;
		} ))
	 
	{
		// 如果notFull_等待了1s，任务队列还是满的，条件依然没有满足，任务提交失败
		std::cerr << "task queue is full, task submit failed!" << std::endl;
		return Result(sp,false); 
	}
	
	// 如果有空余空间，就把任务放入任务队列中
	taskQue_.emplace(sp); 
	++taskNum_; // 任务数量加1

	// 因为放置了任务，所以通知线程池有任务了 在notEmpty条件变量上通知 线程池现在可以分配任务给线程执行了
	notEmpty_.notify_all(); 

	// 返回任务的Result对象
	return Result(sp);
}


// 定义线程函数  线程池的所有线程会从任务队列中获取任务（消费任务）并执行
void ThreadPool::threadFunc()
{
	for (;;)
	{
		std::shared_ptr<Task> task; 

		{	// 先获取锁
			std::unique_lock<std::mutex> lock(taskQueMtx_);

			// 输出进度
			std::cout << "tid:" << std::this_thread::get_id() << " 尝试获取任务！" << std::endl;

			// 等待notEmpty条件  等待线程池中的任务队列有任务  如果线程池没有任务了，线程就一直等待 不要返回
			notEmpty_.wait(lock, [&]()->bool { return taskQue_.size() > 0;});

			// 输出进度
			std::cout << "tid:" << std::this_thread::get_id() << " 已获取任务！" << std::endl;

			// 从任务队列中获取一个任务
			task = taskQue_.front(); // 任务队列容器的第一个元素（指向任务的智能指针）拷贝赋值给task
			taskQue_.pop();
			--taskNum_; // 任务数量减1

			// 如果取出任务后  任务队列依然有剩余任务 则通知其他因为因任务队列为空而阻塞的线程：notEmpty.wait()
			if (taskQue_.size() > 0)
			{
				notEmpty_.notify_all();
			}

			// 取出任务后 进行通知 通知可以继续提交生产任务了
			notFull_.notify_all(); // 通知任务队列有空间了 唤醒那些因为任务队列满而阻塞的线程：notFull.wait()

		}   // **线程获取任务后，就该释放锁了

		// **再去执行任务 否则用户也无法访问任务队列，其他线程也无法访问队列获取任务
		if (task != nullptr) // 健壮性
		{
			task->run();
		}
	}
}


// 开启线程池
void ThreadPool::startThreadPool(size_t initThreadSize)
{    
	// 设置初始线程池的线程数量
	initThreadSize_ = initThreadSize; 
	 
	// 一次性集中创建所有线程对象
	for (int i = 0;i < initThreadSize_;i++) 
	{
		// 创建thread线程的时候，把线程函数给到thread线程对象
		// 线程函数是一个函数对象，std::bind把成员函数ThreadPool::threadFunc绑定到当前对象this(ThreadPool实例)上
		auto funcptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this));
		threads_.emplace_back(std::move(funcptr)); // unique_ptr只允许移动，不能拷贝
	}
	
	// tips:unique_ptr是C++11引入的独占智能指针，封装了delete函数，指针对象析构时会自动释放new出来的内存，避免内存泄漏
	
	// 依次启动所有线程      std::vector<Thread*> threads_
	for (int i = 0;i < initThreadSize_;i++)
	{
		
		threads_[i]->startThread(); // 线程启动了，需要去执行一个线程函数
	}
} 
			//////////////////////////////线程池方法实现END//////////////////////////////////


			//////////////////////////////线程方法实现BEGIN//////////////////////////////////

// 线程的构造函数
Thread::Thread(ThreadFunc func) :func_(func)
{}

// 线程的析构函数
Thread::~Thread() 
{}

// 启动线程(实际创建std::thread 线程对象) 调用该方法的时候，线程就会开始执行
void Thread::startThread() 
{
	std::thread t(func_); //  创建一个c++11的thread对象来执行一个线程函数
	t.detach(); // 设置分离线程  Linux：pthread_detach 设置pthread_t 设置为分离线程
	//tips:startThread() 函数结束后，线程对象t不应该随着作用域结束而析构，而是继续执行到结束，再析构
}
// tips：
// 该项目定义的线程函数threadFunc()是来自于ThreadPool类下的，
// 这就需要通过绑定器std::bind把ThreadPool类的成员函数threadFunc绑定到当前对象this（ThreadPool的实例）上，
// 再插入到线程列表中，创建线程对象，再去执行线程函数。
// 否则，编译器根本不知道调用的是哪个线程池实例的线程函数

			//////////////////////////////线程方法实现END//////////////////////////////////
			

			//////////////////////////////Task方法实现BEGIN////////////////////////////////

Task::Task()
{
	result_ = nullptr; 
}

void Task::setResult(Result* res)
{
	result_ = res;
}

void Task::exec()
{
	if (result_ != nullptr)
	{
		result_->setVal(run());  // 这里发生多态调用
	}
	
}


			//////////////////////////////Task方法实现END////////////////////////////////



			//////////////////////////////Result方法实现BEGIN//////////////////////////////////
Result::Result(std::shared_ptr<Task> taskPtr, bool isValid )
	: taskPtr_(taskPtr)
	, isVaild_(isValid)
{
	taskPtr_->setResult(this);
}

// get方法定义   这是用户调用的
Any Result::get() 
{
	if (!isVaild_)
	{
		return ""; // 若无效 则返回空字符串
	}

	// 必须获取信号量(用户提交的任务执行完毕)才可以return 否则就会阻塞用户主线程
	sem_.wait();

	return std::move(any_);
}
	/*
	tips：为什么 需要使用move ?
	Any类中有unique_ptr指针,不支持拷贝构造/赋值,Any类同时也显示禁止了拷贝构造（Any(const Any&) = delete），
	而get方法的return any_会尝试调用any_对象的拷贝构造创建一个临时对象，
	使用move会调用Any类的移动构造函数（Any(Any&&)）转移any_资源所有权给临时对象，
	（即 base_ 指向的派生类对象）高效地转移给返回值，避免深拷贝。
	*/

void Result::setVal(Any any)
{
	this->any_ = std::move(any);// 无法拷贝赋值 应移动赋值 
	
	// 获取了任务的返回值 增加信号量资源
	sem_.post();
}

	       //////////////////////////////Result方法实现END//////////////////////////////////